import type { Schema } from "@rivet/model";
import type { Plugin } from "@rivet/state";
import type { Editor } from "./editor.ts";
import type {
  AnyConfig,
  ExtensionAttribute,
  Extensions,
  RawCommands,
  RawSelectors,
} from "./types.ts";
import { getSchemaByResolvedExtensions } from "./helpers/getSchemaByResolvedExtensions.ts";
import { getExtensionField } from "./helpers/getExtensionField.ts";
import { findDuplicates } from "./utilities/findDuplicates.ts";
import { getSchemaTypeByName } from "./helpers/getSchemaTypeByName.ts";
import { getAttributesFromExtensions } from "./helpers/getAttributesFromExtensions.ts";
/*
扩展管理器 负责解析扩展 节点和标记以及插件
*/
export class ExtensionManager {
  editor: Editor;
  schema: Schema;
  extensions: Extensions;
  constructor(extensions: Extensions, editor: Editor) {
    this.editor = editor;
    this.extensions = ExtensionManager.resolve(extensions);
    this.schema = getSchemaByResolvedExtensions(this.extensions);
    this.extensions.forEach((extension) => {
      this.editor.extensionStorage[extension.name] = extension.storage;
    });
  }
  get selectors(): RawSelectors {
    return this.extensions.reduce((selectors, extension) => {
      const context = {
        name: extension.name,
        options: extension.options,
        storage: extension.storage,
        editor: this.editor,
        type: getSchemaTypeByName(extension.name, this.schema),
      };

      const addSelectors = getExtensionField<AnyConfig["addSelectors"]>(
        extension,
        "addSelectors",
        context,
      );

      if (!addSelectors) {
        return selectors;
      }

      return {
        ...selectors,
        ...addSelectors(),
      };
    }, {} as RawSelectors);
  }
  get commands(): RawCommands {
    return this.extensions.reduce((commands, extension) => {
      const context = {
        name: extension.name,
        options: extension.options,
        storage: extension.storage,
        editor: this.editor,
        type: getSchemaTypeByName(extension.name, this.schema),
      };

      const addCommands = getExtensionField<AnyConfig["addCommands"]>(
        extension,
        "addCommands",
        context,
      );

      if (!addCommands) {
        return commands;
      }

      return {
        ...commands,
        ...addCommands(),
      };
    }, {} as RawCommands);
  }
  /*
  获取所有插件
  */
  get plugins(): Plugin[] {
    const { editor } = this;
    const extensions = ExtensionManager.sort([...this.extensions].reverse());
    const allPlugins = extensions.map((extension) => {
      const plugins: Plugin[] = [];
      const context = {
        name: extension.name,
        options: extension.options,
        storage: extension.storage,
        editor,
        type: getSchemaTypeByName(extension.name, this.schema),
      };
      const addRivetPlugins = getExtensionField<AnyConfig["addRivetPlugins"]>(
        extension,
        "addRivetPlugins",
        context,
      );
      if (addRivetPlugins) {
        const rivetPlugins = addRivetPlugins();

        plugins.push(...rivetPlugins);
      }
      return plugins;
    }).flat();
    return [...allPlugins];
  }
  static resolve(extensions: Extensions): Extensions {
    const resolvedExtensions = ExtensionManager.sort(
      ExtensionManager.flatten(extensions),
    );
    const duplicatedNames = findDuplicates(
      resolvedExtensions.map((extension) => extension.name),
    );

    if (duplicatedNames.length) {
      console.warn(
        `[rivet warn]: Duplicate extension names found: [${
          duplicatedNames.map((item) => `'${item}'`).join(", ")
        }]. This can lead to issues.`,
      );
    }

    return resolvedExtensions;
  }

  static flatten(extensions: Extensions): Extensions {
    return extensions
      .map((extension) => {
        const context = {
          name: extension.name,
          options: extension.options,
          storage: extension.storage,
        };

        const addExtensions = getExtensionField<AnyConfig["addExtensions"]>(
          extension,
          "addExtensions",
          context,
        );

        if (addExtensions) {
          return [
            extension,
            ...this.flatten(addExtensions()),
          ];
        }

        return extension;
      })
      // `Infinity` will break TypeScript so we set a number that is probably high enough
      .flat(10);
  }

  static sort(extensions: Extensions): Extensions {
    const defaultPriority = 100;

    return extensions.sort((a, b) => {
      const priorityA =
        getExtensionField<AnyConfig["priority"]>(a, "priority") ||
        defaultPriority;
      const priorityB =
        getExtensionField<AnyConfig["priority"]>(b, "priority") ||
        defaultPriority;

      if (priorityA > priorityB) {
        return -1;
      }

      if (priorityA < priorityB) {
        return 1;
      }

      return 0;
    });
  }
  get attributes(): ExtensionAttribute[] {
    return getAttributesFromExtensions(this.extensions);
  }
}
